package fun.tan90.trace;


import cn.hutool.core.collection.CollectionUtil;
import cn.hutool.core.date.DateUnit;
import cn.hutool.core.date.DateUtil;
import fun.tan90.trace.model.*;
import fun.tan90.base.util.AccuracyUtil;
import fun.tan90.base.util.WindowAngleUtil;
import fun.tan90.base.util.WindowCalUtil;
import lombok.extern.slf4j.Slf4j;

import java.util.Date;
import java.util.List;
import java.util.Objects;
import java.util.Optional;
import java.util.function.BiConsumer;
import java.util.function.BiFunction;
import java.util.function.Consumer;
import java.util.stream.Collectors;
import java.util.stream.IntStream;

/**
 * 1.5
 * 私有化xxxxOffline 方法
 * 修复锁对象
 * 优化计算粒度
 * 磁北向下取整
 */
@Slf4j
public class TrainComputer {

    private final int hrCount;

    private final int windWindowSize;

    private final BiFunction<String, Double, double[]> standardFun;

    /**
     * 航向窗口大小
     */
    private final int headingWindowSize;
    private final FixedSizeLinkedBlockingDeque<GeneralTrainData> headingTWindowDeque;
    private final FixedSizeLinkedBlockingDeque<GeneralTrainData> headingMWindowDeque;
    /**
     * 真北阈值过滤器
     */
    private final ThresholdFilter headingTThresholdFilter;
    /**
     * 磁北阈值过滤器
     */
    private final ThresholdFilter headingMThresholdFilter;

    /**
     * 船速窗口大小
     */
    private final int speedWindowSize;
    private final FixedSizeLinkedBlockingDeque<GeneralTrainData> speedWindowDeque;
    /**
     * 船速阈值过滤器
     */
    private final ThresholdFilter speedThresholdFilter;

    /**
     * VMG阈值过滤器
     */
    private final ThresholdFilter vmgThresholdFilter;

    /**
     * 船首向窗口大小
     */
    private final int yawWindowSize;
    private final int yawValueRefresh;
    private final int yawCountRefresh;

    /**
     * 平衡窗口大小
     */
    private final int rolWindowSize;
    private final int rolValueRefresh;
    private final int rolCountRefresh;
    private final int rolBalanceDeviation;

    /**
     * 俯仰窗口大小
     */
    private final int pitchWindowSize;
    private final int pitchValueRefresh;
    private final int pitchCountRefresh;
    private final int pitchBalanceDeviation;

    /**
     * 心率窗口大小
     */
    private final int hrWindowSize;
    private final List<FixedSizeLinkedBlockingDeque<GeneralTrainData>> hrWindowDequeList;
    /**
     * 心率阈值过滤器
     */
    private final List<ThresholdFilter> hrThresholdFilterList;

    /**
     * @param windWindowSize        风窗口大小
     * @param speedWindowSize       船速窗口大小
     * @param speedValueRefresh     船速刷新阈值  cm/s
     * @param speedCountRefresh     船速数据量阈值
     * @param vmgValueRefresh       VMG刷新阈值  cm/s
     * @param vmgCountRefresh       VMG数据量阈值
     * @param rolWindowSize         平衡窗口大小
     * @param rolValueRefresh       平衡刷新阈值
     * @param rolCountRefresh       平衡数据量阈值
     * @param rolBalanceDeviation   平衡数据量阈值
     * @param pitchWindowSize       俯仰窗口大小
     * @param pitchValueRefresh     俯仰刷新阈值
     * @param pitchCountRefresh     俯仰数据量阈值
     * @param pitchBalanceDeviation 俯仰数据量阈值
     * @param headingWindowSize     航向窗口大小
     * @param headingValueRefresh   航向刷新阈值
     * @param headingCountRefresh   航向数据量阈值
     * @param yawWindowSize         船首向窗口大小
     * @param yawValueRefresh       船首向刷新阈值
     * @param yawCountRefresh       船首向数据量阈值
     * @param hrWindowSize          心率窗口大小
     * @param hrValueRefresh        心率刷新阈值
     * @param hrCountRefresh        心率数据量阈值
     * @param hrCount               监测运动员心率的数量
     */
    public TrainComputer(
            int windWindowSize,
            int speedWindowSize, double speedValueRefresh, int speedCountRefresh,
            double vmgValueRefresh, int vmgCountRefresh,
            int rolWindowSize, int rolValueRefresh, int rolCountRefresh, int rolBalanceDeviation,
            int pitchWindowSize, int pitchValueRefresh, int pitchCountRefresh, int pitchBalanceDeviation,
            int headingWindowSize, int headingValueRefresh, int headingCountRefresh,
            int yawWindowSize, int yawValueRefresh, int yawCountRefresh,
            int hrWindowSize, int hrValueRefresh, int hrCountRefresh, int hrCount,
            BiFunction<String, Double, double[]> standardFun) {

        this.windWindowSize = windWindowSize;

        this.speedWindowSize = speedWindowSize;
        this.speedWindowDeque = new FixedSizeLinkedBlockingDeque<>(speedWindowSize);
        this.speedThresholdFilter = new ThresholdFilter(speedValueRefresh, speedCountRefresh);

        this.vmgThresholdFilter = new ThresholdFilter(vmgValueRefresh, vmgCountRefresh);

        this.headingWindowSize = headingWindowSize;
        this.headingTWindowDeque = new FixedSizeLinkedBlockingDeque<>(headingWindowSize);
        this.headingTThresholdFilter = new ThresholdFilter(headingValueRefresh * 1.0, headingCountRefresh);
        this.headingMWindowDeque = new FixedSizeLinkedBlockingDeque<>(headingWindowSize);
        this.headingMThresholdFilter = new ThresholdFilter(headingValueRefresh * 1.0, headingCountRefresh);

        this.rolWindowSize = rolWindowSize;
        this.rolValueRefresh = rolValueRefresh;
        this.rolCountRefresh = rolCountRefresh;
        this.rolBalanceDeviation = rolBalanceDeviation;

        this.pitchWindowSize = pitchWindowSize;
        this.pitchValueRefresh = pitchValueRefresh;
        this.pitchCountRefresh = pitchCountRefresh;
        this.pitchBalanceDeviation = pitchBalanceDeviation;

        this.yawWindowSize = yawWindowSize;
        this.yawValueRefresh = yawValueRefresh;
        this.yawCountRefresh = yawCountRefresh;

        this.hrWindowSize = hrWindowSize;
        this.hrWindowDequeList = IntStream.range(0, hrCount).mapToObj(i -> new FixedSizeLinkedBlockingDeque<GeneralTrainData>(hrWindowSize)).collect(Collectors.toList());
        this.hrThresholdFilterList = IntStream.range(0, hrCount).mapToObj(i -> new ThresholdFilter(hrValueRefresh * 1.0, hrCountRefresh)).collect(Collectors.toList());
        this.hrCount = hrCount;

        this.standardFun = standardFun;
    }

    public void windWindowCompute(List<WindData> windDataList, Bi3Consumer<Date, Double, Double> biConsumerMap) {
        FixedSizeLinkedBlockingDeque<WindVectorData> windVectorDataFixedSizeLinkedBlockingDeque = new FixedSizeLinkedBlockingDeque<>(windWindowSize);
        for (WindData windData : windDataList) {
            windVectorDataFixedSizeLinkedBlockingDeque.offerLast(WindowCalUtil.preCompute(windData.getTrainingBoatCode(), windData.getTs(), windData.getDirection(), windData.getSpeed()));
            List<WindVectorData> windVectorDataList = windVectorDataFixedSizeLinkedBlockingDeque.subList(windWindowSize, true);
            if (CollectionUtil.isEmpty(windVectorDataList)) {
                log.debug("未能达到窗口大小，目标窗口大小为{}", windWindowSize);
                continue;
            }

            // 移除不在窗口数据
            Date maxTs = windVectorDataList.get(windWindowSize - 1).getArrivalDate();
            windVectorDataList = windVectorDataList.stream().filter(n -> DateUtil.between(n.getArrivalDate(), maxTs, DateUnit.SECOND) < windWindowSize).collect(Collectors.toList());
            log.debug("风开启窗口计算 目标窗口大小 {} 实际数据大小 {}", windWindowSize, windVectorDataList.size());

            WindWindowData windWindowData = WindowCalUtil.computeWindowData(windVectorDataList);
            biConsumerMap.accept(windWindowData.getArrivalDate(), windWindowData.getSpeed(), windWindowData.getDirection());
        }
    }

    /**
     * 船首向矢量平均
     */
    public void yawWindowCompute(List<YawData> yawDataList, BiConsumer<Date, Double> biConsumer) {
        FixedSizeLinkedBlockingDeque<YawData> linkedBlockingDeque = new FixedSizeLinkedBlockingDeque<>(yawWindowSize);
        ThresholdFilter yawThresholdFilter = new ThresholdFilter(yawValueRefresh * 1.0, yawCountRefresh);
        for (YawData yawData : yawDataList) {
            linkedBlockingDeque.offerLast(yawData);
            List<YawData> dataList = linkedBlockingDeque.subList(yawWindowSize, true);
            if (CollectionUtil.isEmpty(dataList)) {
                log.debug("{} Z轴未能达到窗口大小，目标窗口大小为{}", this.getClass().getSimpleName(), yawWindowSize);
                continue;
            }

            List<AngleVectorData> angleVectorDataList = dataList.stream().filter(n -> Objects.nonNull(n.calValue()))
                    .map(n -> WindowAngleUtil.preCompute(n.getTs(), n.calValue())).collect(Collectors.toList());
            Double angle = null;
            if (CollectionUtil.isNotEmpty(angleVectorDataList)) {
                AngleWindowData angleWindowData = WindowAngleUtil.computeAngleWindowData(angleVectorDataList);
                angle = angleWindowData.getAngle();
            }

            yawThresholdFilter.filterVal(angle, (type, val) -> {
                if (Objects.nonNull(biConsumer)) {
                    biConsumer.accept(yawData.getTs(), val);
                }
            });
        }
    }

    /**
     * 平衡算数平均
     */
    public void rolWindowCompute(List<RolData> rolDataList, BiConsumer<Date, Double> biConsumer) {
        FixedSizeLinkedBlockingDeque<RolData> linkedBlockingDeque = new FixedSizeLinkedBlockingDeque<>(rolWindowSize);
        ThresholdFilter rolThresholdFilter = new ThresholdFilter(rolValueRefresh * 1.0, rolCountRefresh);
        for (RolData rolData : rolDataList) {
            linkedBlockingDeque.offerLast(rolData);
            List<RolData> dataList = linkedBlockingDeque.subList(rolWindowSize, true);
            if (CollectionUtil.isEmpty(dataList)) {
                log.debug("{} X轴未能达到窗口大小，目标窗口大小为{}", this.getClass().getSimpleName(), rolWindowSize);
                continue;
            }
            dataList = dataList.stream().filter(n -> Objects.nonNull(n.calValue())).collect(Collectors.toList());
            Double average = null;
            if (CollectionUtil.isNotEmpty(dataList)) {
                average = dataList.stream().filter(n -> Objects.nonNull(n.calValue())).mapToDouble(BaseData::calValue).average().orElse(0);
            }
            rolThresholdFilter.filterVal(average, (type, val) -> {
                if (Objects.nonNull(biConsumer)) {
                    biConsumer.accept(rolData.getTs(), val);
                }
            });
        }
    }

    /**
     * 俯仰角算数平均
     */
    public void pitchWindowCompute(List<PitchData> pitchDataList, BiConsumer<Date, Double> biConsumer) {
        FixedSizeLinkedBlockingDeque<PitchData> linkedBlockingDeque = new FixedSizeLinkedBlockingDeque<>(pitchWindowSize);
        ThresholdFilter pitchThresholdFilter = new ThresholdFilter(pitchValueRefresh * 1.0, pitchCountRefresh);
        for (PitchData pitchData : pitchDataList) {
            linkedBlockingDeque.offerLast(pitchData);
            List<PitchData> dataList = linkedBlockingDeque.subList(pitchWindowSize, true);
            if (CollectionUtil.isEmpty(dataList)) {
                log.debug("{} Y轴未能达到窗口大小，目标窗口大小为{}", this.getClass().getSimpleName(), pitchWindowSize);
                continue;
            }
            dataList = dataList.stream().filter(n -> Objects.nonNull(n.calValue())).collect(Collectors.toList());
            Double average = null;
            if (CollectionUtil.isNotEmpty(dataList)) {
                average = dataList.stream().filter(n -> Objects.nonNull(n.calValue())).mapToDouble(BaseData::calValue).average().orElse(0);
            }
            pitchThresholdFilter.filterVal(average, (type, val) -> {
                if (Objects.nonNull(biConsumer)) {
                    biConsumer.accept(pitchData.getTs(), val);
                }
            });
        }
    }

    /**
     * 船速算数平均
     */
    public void speedWindowComputeAndFilter(GeneralTrainData generalTrainData) {
        speedWindowDeque.offerLast(generalTrainData);
        List<GeneralTrainData> dataList = speedWindowDeque.subList(speedWindowSize, true);
        if (CollectionUtil.isEmpty(dataList)) {
            log.debug("{} 未能达到窗口大小，目标窗口大小为{}", this.getClass().getSimpleName(), speedWindowSize);
            return;
        }
        dataList = dataList.stream().filter(n -> Objects.nonNull(n.getSpeed())).collect(Collectors.toList());
        Double average = null;
        if (CollectionUtil.isNotEmpty(dataList)) {
            average = dataList.stream().mapToDouble(GeneralTrainData::getSpeed).average().orElse(0);
        }
        speedThresholdFilter.filterVal(average, (type, val) -> {
            if (Objects.nonNull(generalTrainData.getSpeed())) {
                generalTrainData.setSpeed(val);
            }
        });
    }

    /**
     * VMG阈值过滤
     */
    public void vmgFilterCompute(GeneralTrainData generalTrainData) {
        Double vmg = generalTrainData.getVmg();
        vmgThresholdFilter.filterVal(vmg, (type, val) -> {
            if (Objects.nonNull(vmg)) {
                generalTrainData.setVmg(val);
            }
        });
    }

    /**
     * 真北航向矢量平均
     */
    public void headingTWindowComputeAndFilter(GeneralTrainData generalTrainData) {
        headingTWindowDeque.offerLast(generalTrainData);
        List<GeneralTrainData> dataList = headingTWindowDeque.subList(headingWindowSize, true);
        if (CollectionUtil.isEmpty(dataList)) {
            log.debug("{} 未能达到窗口大小，目标窗口大小为{}", this.getClass().getSimpleName(), headingWindowSize);
            return;
        }

        List<AngleVectorData> angleVectorDataList = dataList.stream().filter(n -> Objects.nonNull(n.getHeadingT()))
                .map(n -> WindowAngleUtil.preCompute(n.getTs(), n.getHeadingT())).collect(Collectors.toList());
        Double angle = null;
        if (CollectionUtil.isNotEmpty(angleVectorDataList)) {
            AngleWindowData angleWindowData = WindowAngleUtil.computeAngleWindowData(angleVectorDataList);
            angle = angleWindowData.getAngle();
        }

        headingTThresholdFilter.filterVal(angle, true, (type, val) -> {
            if (Objects.nonNull(generalTrainData.getHeadingT())) {
                generalTrainData.setHeadingT(AccuracyUtil.downDirectionInteger(val));
            }
        });
    }

    /**
     * 磁北航向矢量平均
     */
    public void headingMWindowComputeAndFilter(GeneralTrainData generalTrainData) {
        headingMWindowDeque.offerLast(generalTrainData);
        List<GeneralTrainData> dataList = headingMWindowDeque.subList(headingWindowSize, true);
        if (CollectionUtil.isEmpty(dataList)) {
            log.debug("{} 未能达到窗口大小，目标窗口大小为{}", this.getClass().getSimpleName(), headingWindowSize);
            return;
        }

        List<AngleVectorData> angleVectorDataList = dataList.stream().filter(n -> Objects.nonNull(n.getHeadingM()))
                .map(n -> WindowAngleUtil.preCompute(n.getTs(), n.getHeadingM())).collect(Collectors.toList());
        Double angle = null;
        if (CollectionUtil.isNotEmpty(angleVectorDataList)) {
            AngleWindowData angleWindowData = WindowAngleUtil.computeAngleWindowData(angleVectorDataList);
            angle = angleWindowData.getAngle();
        }

        headingMThresholdFilter.filterVal(angle, true, (type, val) -> {
            if (Objects.nonNull(generalTrainData.getHeadingM())) {
                generalTrainData.setHeadingM(AccuracyUtil.downDirectionInteger(val));
            }
        });
    }

    /**
     * 心率算数平均
     */
    public void hrWindowComputeAndFilter(GeneralTrainData generalTrainData) {
        List<Integer> hrList = generalTrainData.getHr();
        for (int i = 0; i < hrList.size(); i++) {
            hrWindowDequeList.get(i).offerLast(generalTrainData);
            List<GeneralTrainData> dataList = hrWindowDequeList.get(i).subList(hrWindowSize, true);
            if (CollectionUtil.isEmpty(dataList)) {
                log.debug("{} 未能达到窗口大小，目标窗口大小为{}", this.getClass().getSimpleName(), hrWindowSize);
                continue;
            }
            dataList = dataList.stream().filter(Objects::nonNull).collect(Collectors.toList());
            int finalI = i;
            Double average = null;
            if (CollectionUtil.isNotEmpty(dataList)) {
                average = dataList.stream().mapToInt(n -> n.getHr().get(finalI)).average().orElse(0);
            }
            hrThresholdFilterList.get(i).filterVal(average, (type, val) -> {
                generalTrainData.getHr().set(finalI, AccuracyUtil.downHeartrateInteger(val));
            });
        }
    }

    public void compute(Date ts, Double longitude, Double latitude, Integer headingT, Double declination, Double speed,
                        Integer yaw, Integer rol, Integer pitch, List<Integer> hrList,
                        String trainingBoatCode, Double windSpeed, Integer windDirection, Double customWindSpeed, Integer customWindDirection,
                        Consumer<GeneralTrainData> consumer) {
        Integer headingM = Optional.ofNullable(headingT).map(n ->
                AccuracyUtil.downDirectionInteger((headingT - Optional.ofNullable(declination).orElse(0.0D)) % 360)).orElse(null);

        GeneralTrainData generalTrainData = new GeneralTrainData(ts, hrCount, rolBalanceDeviation, pitchBalanceDeviation, standardFun);

        // GPS数据
        generalTrainData.setLongitude(longitude);
        generalTrainData.setLatitude(latitude);
        generalTrainData.setHeadingT(headingT);
        generalTrainData.setHeadingM(headingM);
        generalTrainData.setSpeed(speed);

        // 风数据
        generalTrainData.setWindSourceBoatCode(trainingBoatCode);
        generalTrainData.setWindSpeed(windSpeed);
        generalTrainData.setWindDirection(windDirection);
        generalTrainData.setCustomWindSpeed(customWindSpeed);
        generalTrainData.setCustomWindDirection(customWindDirection);

        // 姿态数据
        generalTrainData.setYaw(yaw);
        generalTrainData.setRol(rol);
        generalTrainData.setPitch(pitch);

        // 心率数据
        if (CollectionUtil.isNotEmpty(hrList)) {
            for (int i = 0; i < hrList.size(); i++) {
                generalTrainData.getHr().set(i, hrList.get(i));
            }
        }
        generalTrainData.compute();

        consumer.accept(generalTrainData);
    }
}
